/* 
 *	Copyright (C) 2007 cooleyes
 *	eyes.cooleyes@gmail.com 
 *
 *  This Program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *   
 *  This Program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *   
 *  You should have received a copy of the GNU General Public License
 *  along with GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *  http://www.gnu.org/copyleft/gpl.html
 *
 */
 
#include "aa3_parser.h"
#include "common/mem64.h"
#include <string.h>

Aa3Parser::Aa3Parser() {
	initialize();
};

Aa3Parser::~Aa3Parser() {
	close();
};

void Aa3Parser::initialize() {
	is_open = false;
	
	total_samples = 0;
	total_frames = 0;
	
	frame_index = 0;
	reader = 0;
	output_buffer = 0;
	read_buffer = 0;
	
	codec = 0;
	codec_initialize = false;
	
	memset(&init_data, 0, sizeof(AudioCodecInitData));
}

void Aa3Parser::finalize() {
	if ( reader ) {
		buffered_reader_close(reader);
	}
	if ( frame_index ) {
		free(frame_index);
	}
	if ( output_buffer ) {
		free(output_buffer);
	}
	if ( read_buffer ) {
		free(read_buffer);
	}
	if ( codec ) {
		if ( codec_initialize )
			codec->finalize();
		delete codec;
	}
	initialize();
};

char* Aa3Parser::skip_id3v2_and_get_header_info() {
	uint8_t aa3_header_buffer[4];
	if ( buffered_reader_read(reader, aa3_header_buffer, 4) != 4 ) {
		finalize();
		return "Aa3Parser : read AA3 header fail";
	}
	while(1) {
		//OpenMG's id3v2 tag is "ea3" , not "ID3"
		if ( aa3_header_buffer[0] == 'e' && aa3_header_buffer[1] == 'a' && aa3_header_buffer[2] == '3' ) {
			uint8_t id3v2_buffer[6];
			if ( buffered_reader_read(reader, id3v2_buffer, 6) != 6 ) {
				finalize();
				return "Aa3Parser : read ID3v2 fail";
			}
			uint32_t id3v2_size, temp_value;
			temp_value = id3v2_buffer[2];
			temp_value = ( temp_value & 0x7F ) << 21;
			id3v2_size = temp_value;
			temp_value = id3v2_buffer[3];
			temp_value = ( temp_value & 0x7F ) << 14;
			id3v2_size = id3v2_size | temp_value;
			temp_value = id3v2_buffer[4];
			temp_value = ( temp_value & 0x7F ) << 7;
			id3v2_size = id3v2_size | temp_value;
			temp_value = id3v2_buffer[5];
			temp_value = ( temp_value & 0x7F ) ;
			id3v2_size = id3v2_size | temp_value;
			buffered_reader_seek(reader, buffered_reader_position(reader)+id3v2_size);
			if ( buffered_reader_read(reader, aa3_header_buffer, 4) != 4 ) {
				finalize();
				return "Aa3Parser : read AA3 header fail";
			}
			continue;
		}
		if ( aa3_header_buffer[0] != 0x45 || aa3_header_buffer[1] != 0x41 || aa3_header_buffer[2] != 0x33 || aa3_header_buffer[3] != 0x01 ) {
			finalize();
			return "Aa3Parser : AA3 header error";
		}
		uint8_t ea3_header[0x5C];
		if ( buffered_reader_read( reader, ea3_header, 0x5C ) != 0x5C ) {
			finalize();
			return "Aa3Parser : read the AA3 header fail"; 
		}
		atrac3_plus_flag[0] = ea3_header[0x1E];
		atrac3_plus_flag[1] = ea3_header[0x1F];
	
		atrac3_type = (ea3_header[0x1C] == 0x00) ? 0x270 : ((ea3_header[0x1C] == 0x01) ? 0xFFFE : 0x0);
	
		if ( atrac3_type != 0x270 && atrac3_type != 0xFFFE ) {
			finalize();
			return "Aa3Parser : Atrac3 type error";
		}
		
		uint32_t ea3_info = ea3_header[0x1D];
		ea3_info = (ea3_info << 8) | ea3_header[0x1E];
		ea3_info = (ea3_info << 8) | ea3_header[0x1F];
	
		init_data.channels = 2;
		
		switch ((ea3_info >> 13) & 7) {
			case 0:
				init_data.samplerate = 32000;
				break;
			case 1:
				init_data.samplerate = 44100;
				break;
			case 2:
				init_data.samplerate = 48000;
				break;
			default:
				init_data.samplerate = 0;
		} 
	
		init_data.sample_bits = 0x10;
		if ( atrac3_type == 0x270 )
			init_data.samples_per_frame = 1024;
		else
			init_data.samples_per_frame = 2048;
		init_data.samples_per_decoded = init_data.samples_per_frame;
	
		if ( atrac3_type == 0xFFFE ) {
			init_data.frame_align = 8ul * (1ul + (ea3_info & 0x03FF));
		}
		else {
			init_data.frame_align = 8ul * (ea3_info & 0x03FF);
		}
		aa3_data_start = buffered_reader_position(reader);
		break;
	}
	return 0;	
};

char* Aa3Parser::open(const char* filename) {
	
	close();
	
	reader = buffered_reader_open(filename, 65536, 1);
	if ( !reader ) {
		finalize();
		return "Aa3Parser : open buffered_reader fail";
	}
	
	char* result;
	buffered_reader_seek(reader, 0);
	result = skip_id3v2_and_get_header_info();
	if ( result != 0 )
		return result;
	
	aa3_data_length = buffered_reader_length(reader) - aa3_data_start;
	buffered_reader_seek(reader, aa3_data_start);	
	
	total_frames = aa3_data_length / init_data.frame_align;	
	total_samples = total_frames * init_data.samples_per_frame;
	if ( total_frames == 0 ) {
		finalize();
		return "Aa3Parser : total_frames = 0";
	}
	frame_index = (AudioFrameIndex*)malloc_64(total_frames * sizeof(AudioFrameIndex));
	if ( frame_index == 0 ) {
		finalize();
		return "Aa3Parser : malloc seek_table fail";
	}
	uint32_t i;
	frame_index[0].frame_position = aa3_data_start;
	frame_index[0].frame_size = init_data.frame_align;
	for( i = 1; i < total_frames; i++ ) {
		frame_index[i].frame_position = frame_index[i-1].frame_position + frame_index[i-1].frame_size;
		frame_index[i].frame_size = init_data.frame_align; 
	}	
	
	output_buffer = malloc_64(init_data.samples_per_frame<<2);
	if ( output_buffer == 0 ) {
		finalize();
		return "Aa3Parser : malloc output_buffer fail";
	}
	
	if ( atrac3_type == 0x270 )
		read_buffer = malloc_64(0x180);
	else
		read_buffer = malloc_64(init_data.frame_align + 8);
	if ( read_buffer == 0 ) {
		finalize();
		return "Aa3Parser : malloc read_buffer fail";
	}
	
	codec = AudioCodec::create_audio_codec(AudioCodec::ATRAC3);
	if ( !codec ) {
		finalize();
		return "Aa3Parser : create audio codec fail";
	}
	result = codec->initialize(&init_data);
	if ( result ) {
		finalize();
		return result;
	}
	codec_initialize = true;
	
	is_open = true;
	return 0;
};

void Aa3Parser::close() {
	if ( is_open ) {
		finalize();
	}
};
	
uint32_t Aa3Parser::get_samplerate() {
	return init_data.samplerate;
};

uint32_t Aa3Parser::get_channels() {
	return init_data.channels;
};

uint64_t Aa3Parser::get_duration() {
	uint64_t duration = 1000000LL*total_samples/init_data.samplerate;
	return duration;
};
	
uint32_t Aa3Parser::get_samples_per_frame() {
	return init_data.samples_per_frame;
};

uint32_t Aa3Parser::get_max_samples_per_frame() {
	return init_data.samples_per_frame;
};

uint64_t Aa3Parser::seek_time(uint64_t time) {
	uint32_t samples = (uint32_t)(time*init_data.samplerate/1000000LL);
	if ( samples > total_samples )
		samples = total_samples;
	
	uint32_t frame = samples/init_data.samples_per_frame;
	
	if (frame >= total_frames)
		frame = total_frames - 1;
	
	buffered_reader_seek(reader, frame_index[frame].frame_position);
	
	uint64_t current = 1000000LL*frame*init_data.samples_per_frame/init_data.samplerate;
	return current;
};


char* Aa3Parser::get_frame(void** pcm, uint32_t* samples) {
	
	if ( !is_open )
		return "Aa3Parser : parser is not open";
	
	uint32_t read_size;
	if ( atrac3_type == 0x270 ) {
		memset(read_buffer, 0, 0x180);
		read_size = buffered_reader_read(reader, read_buffer, init_data.frame_align);
	}
	else {
		memset(read_buffer, 0, 8+init_data.frame_align);
		uint8_t* p = (uint8_t*)read_buffer;
		p[0] = 0x0F;
		p[1] = 0xD0;
		p[2] = atrac3_plus_flag[0];
		p[3] = atrac3_plus_flag[1];
		read_size = buffered_reader_read(reader, (uint8_t*)read_buffer+8, init_data.frame_align);
	}
	if ( read_size != init_data.frame_align ) {
		*pcm = output_buffer;
		*samples = 0;
	}
	else {
		memset(output_buffer, 0, init_data.samples_per_frame << 2);
		int32_t get_samples = codec->decode(read_buffer, (atrac3_type == 0xFFFE ? (read_size+8) : read_size), output_buffer);
		*samples = (get_samples<0) ? 0 : get_samples;
		*pcm = output_buffer;
	}
	
	return 0;
};




